package main

import (
	"design_pattern/behavioral_patterns/chain_of_responsibility"
	"design_pattern/behavioral_patterns/command"
	"design_pattern/behavioral_patterns/iterator"
	"design_pattern/behavioral_patterns/mediator"
	"design_pattern/behavioral_patterns/memento"
	"design_pattern/behavioral_patterns/observer"
	"design_pattern/behavioral_patterns/state"
	"design_pattern/behavioral_patterns/strategy"
	"design_pattern/behavioral_patterns/template_method"
	"design_pattern/behavioral_patterns/visitor"
	"design_pattern/creational_patterns/abstract_factory"
	"design_pattern/creational_patterns/builder"
	"design_pattern/creational_patterns/factory_method"
	"design_pattern/creational_patterns/prototype"
	"design_pattern/creational_patterns/safe_singleton"
	"design_pattern/creational_patterns/singleton"
	"design_pattern/structural_patterns/adapter"
	"design_pattern/structural_patterns/birdge"
	"design_pattern/structural_patterns/composite"
	"design_pattern/structural_patterns/decorator"
	"design_pattern/structural_patterns/facade"
	"design_pattern/structural_patterns/flyweight"
	"design_pattern/structural_patterns/proxy"
	"fmt"
	"log"
)

func main() {
	//===================================== abstract_factory ==========================================
	adidasFactory, _ := abstract_factory.GetSportsFactory("adidas")
	nikeFactory, _ := abstract_factory.GetSportsFactory("nike")

	nikeShoe := nikeFactory.MakeShoe()
	nikeShirt := nikeFactory.MakeShirt()

	adidasShoe := adidasFactory.MakeShoe()
	adidasShirt := adidasFactory.MakeShirt()

	fmt.Printf("\nAbstract Factory\n")

	fmt.Printf("nikeShoe.logo: %s\n", nikeShoe.GetLogo())
	fmt.Printf("nikeShoe.size: %d\n", nikeShoe.GetSize())

	fmt.Printf("nikeShirt.logo: %s\n", nikeShirt.GetLogo())
	fmt.Printf("nikeShirt.size: %d\n", nikeShirt.GetSize())

	fmt.Printf("adidasShoe.logo: %s\n", adidasShoe.GetLogo())
	fmt.Printf("adidasShoe.size: %d\n", adidasShoe.GetSize())

	fmt.Printf("adidasShirt.logo: %s\n", adidasShirt.GetLogo())
	fmt.Printf("adidasShirt.size: %d\n", adidasShirt.GetSize())
	//===================================== abstract_factory ==========================================

	//===================================== builder ==========================================
	fmt.Printf("\nBuilder\n")
	normalBuilder, _ := builder.GetBuilder("normal")
	iglooBuilder, _ := builder.GetBuilder("igloo")

	anotherNormalBuilder, _ := builder.GetBuilder("normal")

	director := builder.NewDirector(normalBuilder)
	normalHouse := director.BuildHouse()

	fmt.Printf("normalHouse.doorType: %s\n", normalHouse.GetDoorType())
	fmt.Printf("normalHouse.windowType: %s\n", normalHouse.GetWindowType())
	fmt.Printf("normalHouse.floor: %d\n", normalHouse.GetFloor())

	director.SetBuilder(iglooBuilder)
	iglooHouse := director.BuildHouse()

	fmt.Printf("iglooHouse.doorType: %s\n", iglooHouse.GetDoorType())
	fmt.Printf("iglooHouse.windowType: %s\n", iglooHouse.GetWindowType())
	fmt.Printf("iglooHouse.floor: %d\n", iglooHouse.GetFloor())

	director.SetBuilder(anotherNormalBuilder)
	newHouse := director.BuildHouse()
	fmt.Printf("%s\n", newHouse.GetDoorType())
	//===================================== builder ==========================================

	//===================================== factory_method ==========================================

	fmt.Println("\nFactory")
	ak47, _ := factory_method.GetGun("ak47")
	musket, _ := factory_method.GetGun("musket")

	fmt.Printf("ak47.name: %s\n", ak47.GetName())
	fmt.Printf("ak47.power: %d\n", ak47.GetPower())

	fmt.Printf("musket.name: %s\n", musket.GetName())
	fmt.Printf("musket.power: %d\n", musket.GetPower())
	//===================================== factory_method ==========================================

	//===================================== prototype ==========================================
	fmt.Println("\nPrototype")
	file1 := &prototype.File{}
	file1.SetName("File1")
	file2 := &prototype.File{}
	file2.SetName("File2")
	file3 := &prototype.File{}
	file3.SetName("File3")

	folder1 := &prototype.Folder{}
	folder1.SetChildren([]prototype.INode{file1})
	folder1.SetName("Folder1")

	folder2 := &prototype.Folder{}
	folder2.SetChildren([]prototype.INode{folder1, file2, file3})
	folder2.SetName("Folder2")
	fmt.Printf("\nPrinting hierarchy for Folder2\n")
	folder2.Print("____")

	cloneFolder := folder2.Clone()
	fmt.Println("\nPrinting hierarchy for clone Folder2")
	cloneFolder.Print("____")
	//===================================== prototype ==========================================

	//===================================== singleton ==========================================
	fmt.Println("\nSingleton")
	for i := 0; i < 30; i++ {
		go singleton.GetInstance()
	}
	fmt.Println("\nSafeSingleton")
	for i := 0; i < 30; i++ {
		go safe_singleton.GetInstance()
	}
	_, err := fmt.Scanln()
	if err != nil {
		fmt.Println(err)
	}
	//===================================== singleton ==========================================

	//===================================== adapter ==========================================
	fmt.Println("\nAdapter")
	client := &adapter.Client{}
	mac := &adapter.Mac{}

	client.InsertLightningConnectorIntoComputer(mac)

	windowsMachine := &adapter.Windows{}
	windowsMachineAdapter := &adapter.WindowsAdapter{}
	windowsMachineAdapter.SetWindowsMachine(windowsMachine)

	client.InsertLightningConnectorIntoComputer(windowsMachineAdapter)
	//===================================== adapter ==========================================

	//===================================== bridge ==========================================
	fmt.Println("\nBridge")
	hpPrinter := &birdge.HP{}
	epsonPrinter := &birdge.Epson{}

	macComputer := &birdge.Mac{}

	macComputer.SetPrinter(hpPrinter)
	macComputer.Print()
	fmt.Println()

	windowsComputer := &birdge.Windows{}

	windowsComputer.SetPrinter(epsonPrinter)
	windowsComputer.Print()
	fmt.Println()

	windowsComputer.SetPrinter(hpPrinter)
	windowsComputer.Print()
	fmt.Println()
	//===================================== bridge ==========================================

	//===================================== composite ==========================================
	fmt.Println("\nComposite")

	file4 := &composite.File{}
	file4.SetName("File4")
	file5 := &composite.File{}
	file5.SetName("File5")
	file6 := &composite.File{}
	file6.SetName("File6")

	folder3 := &composite.Folder{}
	folder3.SetName("Folder3")

	folder3.Add(file4)

	folder4 := &composite.Folder{}
	folder4.SetName("Folder4")

	folder4.Add(file5)
	folder4.Add(file6)
	folder4.Add(folder3)

	folder4.Search("rose")

	//===================================== composite ==========================================

	//===================================== decorator ==========================================
	fmt.Println("\nDecorator")

	pizza := &decorator.VeggieMania{}

	pizzaWithCheese := &decorator.CheeseTopping{Pizza: pizza}
	pizzaWithCheeseAndTomato := &decorator.TomatoTopping{Pizza: pizzaWithCheese}

	fmt.Printf("Price of veggieMania with cheese and tomato toppings: %d\n", pizzaWithCheeseAndTomato.GetPrice())

	//===================================== decorator ==========================================

	//===================================== facade ==========================================
	fmt.Println("\nFacade")
	walletFacade := facade.NewWalletFacade("abc", 1234)
	fmt.Println()

	err = walletFacade.AddMoneyToWallet("abc", 1234, 10)
	if err != nil {
		log.Fatalf("Error: %s\n", err.Error())
	}
	fmt.Println()

	err = walletFacade.DeductMoneyFromWallet("abc", 1234, 5)
	if err != nil {
		log.Fatalf("Error: %s\n", err.Error())
	}
	fmt.Println()


	//===================================== facade ==========================================

	//===================================== flyweight ==========================================
	fmt.Println("\nFlyweight")

	game := flyweight.NewGame()

	game.AddTerrorist(flyweight.TerroristDressType)
	game.AddTerrorist(flyweight.TerroristDressType)
	game.AddTerrorist(flyweight.TerroristDressType)
	game.AddTerrorist(flyweight.TerroristDressType)

	game.AddCounterTerrorist(flyweight.CounterTerroristDressType)
	game.AddCounterTerrorist(flyweight.CounterTerroristDressType)
	game.AddCounterTerrorist(flyweight.CounterTerroristDressType)

	dressFactoryInstance := flyweight.GetDressFactorySingleInstance()

	for dressType, dress := range dressFactoryInstance.GetDressMap() {
		fmt.Printf("DressColorType: %s\nDressColor: %s\n", dressType, dress.GetColor())
	}
	//===================================== flyweight ==========================================

	//===================================== proxy ==========================================
	fmt.Println("\nProxy")

	nginxServer := proxy.NewNginxServer()
	appStatusURL := "/app/status"
	createuserURL := "/create/user"

	httpCode, body := nginxServer.HandleRequest(appStatusURL, "GET")
	fmt.Printf("\nUrl: %s\nHttpCode: %d\nBody: %s\n", appStatusURL, httpCode, body)

	httpCode, body = nginxServer.HandleRequest(appStatusURL, "GET")
	fmt.Printf("\nUrl: %s\nHttpCode: %d\nBody: %s\n", appStatusURL, httpCode, body)

	httpCode, body = nginxServer.HandleRequest(appStatusURL, "GET")
	fmt.Printf("\nUrl: %s\nHttpCode: %d\nBody: %s\n", appStatusURL, httpCode, body)

	httpCode, body = nginxServer.HandleRequest(createuserURL, "GET")
	fmt.Printf("\nUrl: %s\nHttpCode: %d\nBody: %s\n", appStatusURL, httpCode, body)

	httpCode, body = nginxServer.HandleRequest(createuserURL, "GET")
	fmt.Printf("\nUrl: %s\nHttpCode: %d\nBody: %s\n", appStatusURL, httpCode, body)


	//===================================== proxy ==========================================

	//===================================== chain of responsibility ==========================================
	fmt.Printf("\nChain of Responsibility\n")

	cashier := &chain_of_responsibility.Cashier{}

	medical := &chain_of_responsibility.Medical{}
	medical.SetNext(cashier)

	doctor := &chain_of_responsibility.Doctor{}
	doctor.SetNext(medical)

	reception := &chain_of_responsibility.Reception{}
	reception.SetNext(doctor)

	patient := &chain_of_responsibility.Patient{}
	patient.SetName("foo bar")

	reception.Execute(patient)

	//===================================== chain of responsibility ==========================================

	//===================================== command ==========================================
	fmt.Println("\nCommand")
	tv := &command.TV{}

	onCommand := &command.OnCommand{Device: tv}
	offCommand := &command.OffCommand{Device: tv}

	onButton := &command.Button{Command: onCommand}
	onButton.Press()

	offButton := &command.Button{Command: offCommand}
	offButton.Press()
	//===================================== command ==========================================

	//===================================== iterator ==========================================
	fmt.Println("\nIterator")

	user1 := &iterator.User{
		Name: "a",
		Age:  30,
	}
	user2 := &iterator.User{
		Name: "b",
		Age:  20,
	}

	userCollection := &iterator.UserCollection{
		User: []*iterator.User{user1, user2},
	}

	_iterator := userCollection.CreateIterator()

	for _iterator.HasNext() {
		user := _iterator.GetNext()
		fmt.Printf("User is %+v\n", user)
	}
	//===================================== iterator ==========================================

	//===================================== mediator ==========================================
	fmt.Println("\nMediator")

	stationManager := mediator.NewStationManager()
	passengerTrain := &mediator.PassengerTrain{Mediator: stationManager}
	freightTrain := &mediator.FreightTrain{Mediator: stationManager}

	passengerTrain.Arrive()
	freightTrain.Arrive()
	passengerTrain.Depart()
	//===================================== mediator ==========================================


	//===================================== memento ==========================================
	fmt.Println("\nMemento")

	caretaker := &memento.Caretaker{}

	originator := &memento.Originator{}
	originator.SetState("A")

	fmt.Printf("Originator Current State: %s\n", originator.GetState())
	caretaker.AddMemento(originator.CreateMemento())

	originator.SetState("B")
	fmt.Printf("Originator Current State: %s\n", originator.GetState())
	caretaker.AddMemento(originator.CreateMemento())

	originator.SetState("C")
	fmt.Printf("Originator Current State: %s\n", originator.GetState())
	caretaker.AddMemento(originator.CreateMemento())

	originator.RestoreMemento(caretaker.GetMemento(1))
	fmt.Printf("Originator Current State: %s\n", originator.GetState())

	originator.RestoreMemento(caretaker.GetMemento(0))
	fmt.Printf("Originator Current State: %s\n", originator.GetState())


	//===================================== memento ==========================================

	//===================================== observer ==========================================
	fmt.Println("\nObserver")

	shirtItem := observer.NewItem("GAP Shirt")

	observerFirst := &observer.Customer{}
	observerFirst.SetID("abc@qq.com")

	observerSecond := &observer.Customer{}
	observerSecond.SetID("xyz@163.com")

	shirtItem.Register(observerFirst)
	shirtItem.Register(observerSecond)
	shirtItem.UpdateAvailability()

	fmt.Println()
	shirtItem.Deregister(observerFirst)
	shirtItem.UpdateAvailability()

	//===================================== observer ==========================================

	//===================================== state ==========================================
	fmt.Println("\nState")
	vendingMachine := state.NewVendingMachine(1, 10)
	err = vendingMachine.RequestItem()
	if err != nil {
		log.Fatalf(err.Error())
	}

	err = vendingMachine.InsertMoney(10)
	if err != nil {
		log.Fatalf(err.Error())
	}

	err = vendingMachine.DispenseItem()
	if err != nil {
		log.Fatalf(err.Error())
	}
	fmt.Println()

	err = vendingMachine.AddItem(2)
	if err != nil {
		log.Fatalf(err.Error())
	}
	fmt.Println()

	err = vendingMachine.RequestItem()
	if err != nil {
		log.Fatalf(err.Error())
	}

	err = vendingMachine.InsertMoney(10)
	if err != nil {
		log.Fatalf(err.Error())
	}

	err = vendingMachine.DispenseItem()
	if err != nil {
		log.Fatalf(err.Error())
	}

	//===================================== state ==========================================

	//===================================== strategy ==========================================
	fmt.Println("\nStrategy")
	lfu := &strategy.LFU{}
	cache := strategy.InitCache(lfu)

	cache.Add("a", "1")
	cache.Add("b", "2")
	cache.Add("c", "3")

	lru := &strategy.LRU{}
	cache.SetEvictionAlgo(lru)

	cache.Add("d", "4")

	fifo := &strategy.FIFO{}
	cache.SetEvictionAlgo(fifo)

	cache.Add("e", "5")

	//===================================== strategy ==========================================

	//===================================== template method ==========================================
	fmt.Println("\nTemplate Method")
	smsOTP := &template_method.Sms{}
	o := &template_method.Otp{
		IOtp: smsOTP,
	}
	err = o.GenAndSendOTP(4)
	if err != nil {
		return
	}

	emailOTP := &template_method.Email{}
	o = &template_method.Otp{IOtp: emailOTP}
	err = o.GenAndSendOTP(4)
	if err != nil {
		return
	}

	//===================================== template method ==========================================


	//===================================== visitor ==========================================
	fmt.Println("\nVisitor")
	square := &visitor.Square{}
	circle := &visitor.Circle{}
	rectangle := &visitor.Rectangle{}

	areaCalculator := &visitor.AreaCalculator{}

	square.Accept(areaCalculator)
	circle.Accept(areaCalculator)
	rectangle.Accept(areaCalculator)

	fmt.Println()
	middleCoordinates := &visitor.MiddleCoordinates{}
	square.Accept(middleCoordinates)
	circle.Accept(middleCoordinates)
	rectangle.Accept(middleCoordinates)

	//===================================== visitor ==========================================

}